#
# Copyright (C) Since 2018 ~ 2025, Inc. All Rights Reserved
#
# @Time    : 2025/1/9-上午10:35
# @Author  : Timmy
# @Email   : tangping@cloudstream-tech.com
# @File    : types.py
# @Software: lottery
# @Function:
#
import uuid
from dataclasses import dataclass, field
from datetime import datetime, date

import numpy as np

from .models import (
    Activity as ActivityModel,
    People as PeopleModel, AwardItem,
    ShotEvent as ShotEventModel,
)


@dataclass
class People:
    name: str
    dep: str
    identity: str
    alpha: int = 1
    is_hit: bool = False

    def __str__(self):
        return "%s·%s" % (self.name, self.dep)

    def json(self):
        return {"name": self.name, "dep": self.dep, "icon": None}


@dataclass
class Award:
    name: str
    content: str
    count: int = 1
    lucky: list[People] = field(default_factory=list)
    time: datetime = field(default_factory=datetime.now)

    def lucky_count(self):
        return len(self.lucky)

    def names(self, with_dep=False):
        if with_dep:
            return ','.join([f"{p.name}·{p.dep}" for p in self.lucky])
        return ','.join([f"{p.name}" for p in self.lucky])


@dataclass
class ShotEvent:
    eid: str = field(default_factory=lambda: str(uuid.uuid4()))
    who: list[People] = field(default_factory=list)
    item: Award = None
    time: datetime = field(default_factory=lambda: datetime.now())

    def names(self, with_dep=False) -> str:
        if with_dep:
            return ','.join([f"{p.name}·{p.dep}" for p in self.who])
        return ','.join([f"{p.name}" for p in self.who])


class Activity(object):
    def __init__(self) -> None:
        self.name: str = ""
        self.date: date = date.today()

        self.people: dict[str, People] = {}
        self.awards: dict[str, Award] = {}
        self.events: list[ShotEvent] = []

        self._db_obj: ActivityModel | None = None
        self._current_award: Award | None = None
        self._must: list[People] = []

    async def set_db_object(self, obj: ActivityModel):
        self.awards = dict[str, Award]()
        self.people = dict[str, People]()
        self.events = list[ShotEvent]()
        await self.load_db_by_name(obj.name)
        self._db_obj = obj

    def set_current_award(self, award_name: str):
        self._current_award = self.awards[award_name]

    def current_award(self) -> Award:
        return self._current_award

    # 获取奖项总数
    # def get_award_item_total(self) -> int:
    #     return len(self.awards)

    # 获取实际总奖数
    def get_award_total(self):
        return sum([a.count for a in self.awards.values()])

    # 获取已经抽出的奖数
    def get_award_pop_count(self):
        total = sum([len(a.lucky) for a in self.awards.values()])
        return total

    # 获取总人数
    def get_people_total(self) -> int:
        return len(self.people)

    # 获取中奖人数
    def get_hit_people_total(self) -> int:
        total = 0
        for p in self.people.values():
            if p.is_hit:
                total += 1
        return total

    # 获取总概率
    def get_rate_total(self) -> float:
        people_total = self.get_people_total()
        if people_total > 0:
            return round(self.get_award_total() * 100 / people_total, 2)
        return 0

    def _no_hit_list(self) -> list[People]:
        return [p for p in self.people.values() if p.is_hit is False]

    def next_rate(self, num: int) -> float:
        remaining = len(self._no_hit_list())
        if remaining > 0:
            return round(num * 100 / remaining, 2)
        return 0.0

    def set_must(self, replaceable: bool, *args) -> list[People]:
        result: list[People] = []
        if len(args) == 0:
            return result

        for name in args:
            p = self.people.get(name)
            if p is not None:
                if p.is_hit is False or replaceable:
                    result.append(p)

        for person in result:
            if person not in self._must:
                self._must.append(person)
        return result

    async def shot(self, num=1, replace=False, alpha=False) -> ShotEvent:
        event = ShotEvent(item=self._current_award)

        must = self._must[:]
        if len(must) > 0:
            num = num - len(must)

        people_pool: list[People] = []
        if replace:
            for p in self.people.values():
                if p in must:
                    continue
                if alpha:
                    people_pool.extend([p] * p.alpha)
                else:
                    people_pool.append(p)
        else:
            for p in self.people.values():
                if p.is_hit or p in must:
                    continue
                if alpha:
                    people_pool.extend([p] * p.alpha)
                else:
                    people_pool.append(p)

        result = must[:]
        for i in range(num):
            if len(people_pool) == 0:
                break

            people_pool_nr = np.array(people_pool)
            np.random.shuffle(people_pool_nr)
            persons = list(np.random.choice(people_pool_nr, 1, replace=False))
            result.append(persons[0])
            people_pool.remove(persons[0])

        np.random.shuffle(result)
        result = list(result)
        for p in result:
            p.is_hit = True

        event.who = result
        self._must = []
        await self.add_event(event)
        return event

    async def add_award(self, award: Award):
        if award.name in self.awards:
            raise ValueError(f"Award {award.name} already exists")

        self.awards[award.name] = award
        if self._db_obj:
            await AwardItem.create(
                act=self._db_obj, name=award.name,
                content=award.content, count=award.count, create_at=award.time)

    async def del_award(self, name: str):
        if name in self.awards:
            del self.awards[name]

        if self._db_obj:
            await AwardItem.filter(act=self._db_obj, name=name).delete()

    async def add_people(self, people: list[People]) -> list[People]:
        conflict: list[People] = []
        news: list[People] = []

        for person in people:
            if person.name in self.people:
                conflict.append(person)
            else:
                self.people[person.name] = person
                news.append(person)

        if self._db_obj:
            await PeopleModel.bulk_create([
                PeopleModel(act=self._db_obj, name=p.name, dep=p.dep, identity=p.identity, people=p.alpha)
                for p in news
            ])

        return conflict

    async def del_people(self, name: str):
        if name in self.people:
            del self.people[name]

        if self._db_obj:
            await PeopleModel.filter(act=self._db_obj, name=name).delete()

    def get_event(self, eid: str) -> ShotEvent | None:
        for e in self.events:
            if e.eid == eid:
                return e
        return None

    async def add_event(self, e: ShotEvent):
        self.events.append(e)
        award = self.awards[e.item.name]
        award.lucky += e.who
        award.count = max(award.count, len(award.lucky))

        if self._db_obj:
            who = await PeopleModel.filter(act=self._db_obj, name__in=[p.name for p in e.who])
            # print('---------', who, e.who)
            item = await AwardItem.filter(act=self._db_obj, name=e.item.name).first()
            await ShotEventModel.bulk_create([
                ShotEventModel(eid=e.eid, act=self._db_obj, who=w, item=item, create_at=e.time)
                for w in who
            ])

    async def del_event(self, e: ShotEvent):
        self.events.remove(e)
        award = self.awards[e.item.name]
        for people in e.who:
            award.lucky.remove(people)

        if self._db_obj:
            await ShotEventModel.filter(act=self._db_obj, eid=e.eid).update(cancel_at=datetime.now())

    async def load_db_by_name(self, name):
        obj = await ActivityModel.filter(name=name, enable=True).first()
        if not obj:
            raise ValueError(f"Activity {name} not found")

        self.name = obj.name
        self.date = obj.date
        guys = await PeopleModel.filter(act=obj, enable=True).all()
        for guy in guys:
            self.people[guy.name] = People(
                name=guy.name, dep=guy.dep, identity=guy.identity, alpha=guy.alpha)

        awards = await AwardItem.filter(act=obj, enable=True).order_by('create_at').all()
        for award in awards:
            self.awards[award.name] = Award(
                name=award.name, content=award.content, count=award.count, time=award.create_at)

        shots = await ShotEventModel.filter(act=obj, cancel_at=None).prefetch_related('who', 'item').all()
        events: dict[str, ShotEvent] = {}

        for shot in shots:
            people = self.people.get(shot.who.name)
            if not people:
                continue

            people.is_hit = True

            award = self.awards[shot.item.name]
            award.lucky.append(people)

            if shot.eid not in events:
                events[shot.eid] = ShotEvent(eid=shot.eid, who=[people], item=award, time=shot.create_at)
            else:
                events[shot.eid].who.append(people)

        self.events = sorted(events.values(), key=lambda e: e.time)
        # print(self.awards)
        self._db_obj = obj

    async def save_db(self, force=False):
        if not self.name:
            raise ValueError(f"Activity name is empty")

        if self._db_obj and not force:
            await ActivityModel.filter(id=self._db_obj.id).update(name=self.name, date=self.date)
            return

        if force:
            await ActivityModel.filter(name=self.name).delete()

        act, created = await ActivityModel.update_or_create(
            name=self.name, defaults={'date': self.date})

        if not act:
            raise ValueError(f"Create activity {self.name} failed")

        await AwardItem.bulk_create([
            AwardItem(act=act, name=a.name, content=a.content, count=a.count, create_at=a.time)
            for a in self.awards.values()
        ])

        await PeopleModel.bulk_create([
            PeopleModel(act=act, name=p.name, dep=p.dep, identity=p.identity, alpha=p.alpha)
            for p in self.people.values()
        ])

        # await ShotEventModel.bulk_create([
        #     ShotEventModel(who=s.who, item=s.item, create_at=s.time)
        #     for s in self.events
        # ])

        print(f"Activity {act.name} save ok!")

    async def delete(self):
        await ActivityModel.filter(name=self.name).delete()
